package com.mingqijia.gassafety.authority.dal.provider;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mingqijia.gassafety.authority.api.resp.config.DictionaryItemRespDTO;
import com.mingqijia.gassafety.authority.api.resp.resource.ResourceLiteRespDTO;
import com.mingqijia.gassafety.authority.dal.command.CreateResourceCommand;
import com.mingqijia.gassafety.authority.dal.command.QueryResourceCommand;
import com.mingqijia.gassafety.authority.dal.command.RemoveResourceCommand;
import com.mingqijia.gassafety.authority.dal.command.UpdateResourceCommand;
import com.mingqijia.gassafety.authority.dal.handler.ResourceTranslator;
import com.mingqijia.gassafety.authority.dal.helper.ConfigureHolder;
import com.mingqijia.gassafety.db.entity.auth.ManagerEntry;
import com.mingqijia.gassafety.db.entity.auth.PrivilegeEntry;
import com.mingqijia.gassafety.db.entity.auth.ResourceEntry;
import com.mingqijia.gassafety.db.mapper.auth.ResourceMapper;
import com.mingqijia.gassafety.shared.constant.Constants;
import com.mingqijia.gassafety.shared.cqrs.QueryHandler;
import com.mingqijia.gassafety.shared.enums.ResourceType;
import com.mingqijia.gassafety.shared.enums.RunningState;
import com.mingqijia.gassafety.shared.enums.YesNoEnum;
import com.mingqijia.gassafety.shared.utils.ApplicationUtils;
import com.mingqijia.gassafety.shared.utils.MappingTools;
import com.mingqijia.gassafety.shared.handler.GlobalAuthenticationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 信息CU操作(C-create,U-update)
 * @author Damon S.
 * @version v0.2
 * @date 2021-06-19
 */
@Slf4j
@Component
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class ResourceProvider extends ServiceImpl<ResourceMapper, ResourceEntry> implements QueryHandler<ResourceEntry> {

    private final ResourceTranslator translator;
    private final ConfigureHolder holder;
    private final ManagerProvider mProvider;
    private final PrivilegeProvider provider;

    public Page<ResourceEntry> find(QueryResourceCommand command) {
        LambdaQueryWrapper<ResourceEntry> queryClause = new LambdaQueryWrapper<>();
        Optional.ofNullable(command.getSpId()).ifPresent(
                spId -> queryClause.eq(ResourceEntry::getSpId, spId)
        );
        Optional.ofNullable(command.getName()).ifPresent(
                name -> queryClause.like(ResourceEntry::getName, name)
        );
        Optional.ofNullable(command.getParentId()).ifPresent(
                parentId -> queryClause.eq(ResourceEntry::getParentId, parentId)
        );
        Optional.ofNullable(command.getType()).ifPresent(
                type -> queryClause.eq(ResourceEntry::getType, type.getCode())
        );
        Optional.ofNullable(command.getState()).ifPresent(
                state -> queryClause.eq(ResourceEntry::getState, state.getCode())
        );
        // 除了平台管理员，其他用户只能查看自己权限内的资源
        List<String> resourceIds = getAuthorizedResources(command);
        if (!CollectionUtils.isEmpty(resourceIds)) {
            queryClause.in(ResourceEntry::getId, resourceIds);
        }
        queryClause.eq(ResourceEntry::getIsDeleted, YesNoEnum.N.getCode());
        return this.page(createPage(command), queryClause.orderByAsc(ResourceEntry::getOrdinal));
    }

    @Transactional(rollbackFor = Exception.class)
    public String create(CreateResourceCommand command) {
        LambdaQueryWrapper<ResourceEntry> queryClause = new LambdaQueryWrapper<>();
        queryClause.eq(ResourceEntry::getParentId, Optional.ofNullable(command.getParentId()).orElse(Constants.STR_SLASH))
                .and(qw -> qw.eq(ResourceEntry::getName, command.getName())
                        .or().eq(ResourceEntry::getFuncCode, command.getFuncCode()));
        if (!Objects.equals(Constants.INT_ZERO, this.count(queryClause))) {
            throw new GlobalAuthenticationException("同级菜单已存在");
        }

        // 配置相关信息
        if (ObjectUtils.isEmpty(command.getOrdinal())) {
            command.setOrdinal(Constants.INT_ZERO);
        }
        if (StringUtils.isEmpty(command.getParentId())) {
            command.setParentId(Constants.STR_SLASH);
        }
        if (ObjectUtils.isEmpty(command.getState())) {
            command.setState(RunningState.RUNNING);
        }
        // 默认类型是菜单
        if (ObjectUtils.isEmpty(command.getType()) ||
                ResourceType.NA.equals(command.getType())) {
            command.setType(ResourceType.MENU);
        }
        // 新建资源放在平台SPID上
        command.setSpId(Constants.PLATFORM_SP);
        this.attachOperator2Command(command);

        ResourceEntry entry = translator.toEntry(command);
        if (!this.save(entry)) {
            throw new GlobalAuthenticationException("添加资源失败");
        }
        return entry.getId();
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean update(UpdateResourceCommand command) {
        this.attachOperator2Command(command);

        LambdaUpdateWrapper<ResourceEntry> updateClause = new LambdaUpdateWrapper<>();
        updateClause.eq(ResourceEntry::getId, command.getResId())
                .eq(ResourceEntry::getType, command.getType().getCode());

        ResourceEntry entry = translator.toEntry(command);
        return this.update(entry, updateClause);
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean remove(RemoveResourceCommand command) {
        this.attachOperator2Command(command);

        LambdaUpdateWrapper<ResourceEntry> updateClause = new LambdaUpdateWrapper<>();
        updateClause.in(ResourceEntry::getId, command.getResIds())
                .set(ResourceEntry::getIsDeleted, YesNoEnum.Y.getCode())
                .set(ResourceEntry::getUpdatedBy, command.getUpdatedBy());

        return this.update(updateClause);
    }

    public List<ResourceLiteRespDTO> buildResourceHierarchy(List<String> roleIds) {
        List<PrivilegeEntry> entries = provider.findByRoleIds(roleIds);
        if (CollectionUtils.isEmpty(entries)) {
            return Collections.emptyList();
        }
        Set<String> resourceIds = new HashSet<>();
        entries.forEach(item -> resourceIds.addAll(
                MappingTools.fromRaw(item.getData())
        ));

        QueryWrapper<ResourceEntry> queryClause = new QueryWrapper<>();
        queryClause.lambda().orderByAsc(ResourceEntry::getOrdinal)
                .in(ResourceEntry::getId, resourceIds)
                .eq(ResourceEntry::getState, RunningState.RUNNING.getCode())
                .eq(ResourceEntry::getIsDeleted, YesNoEnum.N.getCode());

        List<ResourceEntry> resourceEntries = this.list(queryClause);
        return this.buildHierarchyFromRoot(resourceEntries);
    }

    public List<ResourceLiteRespDTO> buildResourceHierarchy() {
        QueryWrapper<ResourceEntry> queryClause = new QueryWrapper<>();
        queryClause.lambda().orderByAsc(ResourceEntry::getOrdinal)
                .eq(ResourceEntry::getState, RunningState.RUNNING.getCode())
                .eq(ResourceEntry::getIsDeleted, YesNoEnum.N.getCode());

        List<ResourceEntry> entries = this.list(queryClause);
        return this.buildHierarchyFromRoot(entries);
    }

    private List<String> getAuthorizedResources(QueryResourceCommand command) {
        String userId = ApplicationUtils.getUserId();
        List<String> resources = new ArrayList<>();

        // 除了平台管理员，其他用户只能查看自己权限内的资源
        if (Objects.equals(Constants.PLATFORM_SA, userId)
                || StringUtils.hasText(command.getSpId())) {
            return resources;
        }
        ManagerEntry entry = mProvider.getById(userId);
        if (ObjectUtils.isEmpty(entry)) {
            log.error("查询资源数据失败，用户不合法[userId={}]", userId);
            // 非法用户不能查到任何资源数据，指定一个不存在的ID值
            return new ArrayList<String>() {{ add(Constants.STR_SLASH); }};
        }
        // 获取用户的角色权限
        List<PrivilegeEntry> privileges = provider.findByRoleIds(
                MappingTools.fromRaw(entry.getRoleId())
        );
        // 角色所拥有的资源权限
        privileges.forEach(item -> resources.addAll(
                MappingTools.fromRaw(item.getData())
        ));
        return resources;
    }

    public List<ResourceLiteRespDTO> buildHierarchyFromRoot(List<ResourceEntry> resources) {
        resources = resources.stream().filter(
                item -> !item.getIsDeleted()).collect(Collectors.toList());
        List<ResourceLiteRespDTO> responses = translator.toLiteRespDTO(resources);
        // 对资源进行层级架构重构
        List<ResourceLiteRespDTO> liteResponses = responses.stream().filter(
                item -> Objects.equals(Constants.STR_SLASH, item.getParentId())
        ).sorted(Comparator.comparing(ResourceLiteRespDTO::getOrdinal))
                .collect(Collectors.toList());

        for (ResourceLiteRespDTO resp : liteResponses) {
            this.buildResourceHierarchy(responses, resp);
        }
        return liteResponses;
    }

    /** 构建子菜单 */
    private void buildResourceHierarchy(List<ResourceLiteRespDTO> sources,
                                        ResourceLiteRespDTO target) {
        target.setChildren(sources.stream().filter(
                item -> target.getResId().equalsIgnoreCase(item.getParentId())
        ).sorted(Comparator.comparing(ResourceLiteRespDTO::getOrdinal))
                .collect(Collectors.toList()));

        target.setParentId(null); target.setOrdinal(null);
        if (CollectionUtils.isEmpty(target.getChildren())) {
            return;
        }
        for (ResourceLiteRespDTO resp : target.getChildren()) {
            this.buildResourceHierarchy(sources, resp);
        }
    }

    public List<DictionaryItemRespDTO> findRawOperations() {
        List<DictionaryItemRespDTO> items = holder.getDictionaryByKey(
                Constants.CACHE_KEY_FUNC_CODE);
        return items.stream().sorted(
                Comparator.comparing(DictionaryItemRespDTO::getOrdinal)
        ).collect(Collectors.toList());
    }
}
